function isString(str: any): str is string {
    return typeof str === 'string';
}

function isFunction(fun: any): fun is Function {
    return typeof fun === 'function';
}

interface Listener {
    callback: Function;
    order: number;
}

interface Listeners {
    [key: string]: Listener[] | undefined;
}

export interface Options {
    order?: number;
}

interface OrderOptions {
    order: number;
}

const defaultOptions: OrderOptions = {
    order: 0,
};

class EventKernel<T = MapLike> {
    private _listeners: Listeners = {};
    private _onetimeListeners: Listeners = {};

    /**
     * 获取order在按照由大到小的循序排列的中的索引
     * @param arr Listener[]
     * @param options OrderOptions
     */
    static getIndexByOrder(arr: Listener[], options: OrderOptions) {
        for (let i = 0; i < arr.length; i += 1) {
            if (options.order >= arr[i].order) {
                return i;
            }
        }
        return arr.length;
    }

    on<K extends keyof T>(type: K, listener: (e: T[K]) => void, options?: Options) {
        if (!isString(type) || !isFunction(listener)) {
            console.warn('on: expect on(type: string, listener: Function)');
            return this;
        }
        const arr = this._listeners[type];
        const opts = { ...defaultOptions, ...options };
        if (arr) {
            const index = EventKernel.getIndexByOrder(arr, opts);
            arr.splice(index, 0, { callback: listener, order: opts.order });
        } else {
            this._listeners[type] = [{
                callback: listener,
                order: opts.order,
            }];
        }

        return this;
    }

    once<K extends keyof T>(type: K, listener: (e: T[K]) => void, options?: Options) {
        if (!isString(type) || !isFunction(listener)) {
            console.warn('once: expect on(type: string, listener: Function)');
            return this;
        }
        const arr = this._onetimeListeners[type];
        const opts = { ...defaultOptions, ...options };
        if (arr) {
            const index = EventKernel.getIndexByOrder(arr, opts);
            arr.splice(index, 0, { callback: listener, order: opts.order });
        } else {
            this._onetimeListeners[type] = [{
                callback: listener,
                order: opts.order,
            }];
        }

        return this;
    }

    off<K extends keyof T>(type: K, listener: (e: T[K]) => void) {
        if (!isString(type) || !isFunction(listener)) {
            console.warn('off: expect on(type: string, listener: Function)');
            return this;
        }

        const arr = this._listeners[type] || [];
        for (let i = arr.length - 1; i >= 0; i -= 1) {
            if (arr[i].callback === listener) {
                arr.splice(i, 1);
            }
        }
        arr.length === 0 && delete this._listeners[type];

        const onetimeArr = this._onetimeListeners[type] || [];
        for (let i = onetimeArr.length - 1; i >= 0; i -= 1) {
            if (onetimeArr[i].callback === listener) {
                onetimeArr.splice(i, 1);
            }
        }
        onetimeArr.length === 0 && delete this._onetimeListeners[type];

        return this;
    }

    fire<K extends keyof T>(type: K & string, eventData?: T[K]) {
        const arr = Array.from(this._listeners[type] || []);
        for (let i = 0; i < arr.length; i += 1) {
            arr[i].callback(eventData);
        }

        const onetimeArr = Array.from(this._onetimeListeners[type] || []);
        for (let i = 0; i < onetimeArr.length; i += 1) {
            onetimeArr[i].callback(eventData);
        }
        delete this._onetimeListeners[type];

        return this;
    }

    clearRegister() {
        this._listeners = {};
        this._onetimeListeners = {};
    }

    getListeners<K extends keyof T>(type: K & string) {
        return this._listeners[type] || this._onetimeListeners[type];
    }
}

export default EventKernel;
